package com.xiaoyang.forum.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.xiaoyang.forum.common.code.CacheConstants;
import com.xiaoyang.forum.common.code.Result;
import com.xiaoyang.forum.common.code.ResultCode;
import com.xiaoyang.forum.common.exception.ApplicationException;
import com.xiaoyang.forum.common.util.*;
import com.xiaoyang.forum.domain.dto.UserPasswordDTO;
import com.xiaoyang.forum.domain.dto.UserUpdateDTO;
import com.xiaoyang.forum.domain.pojo.Article;
import com.xiaoyang.forum.domain.pojo.User;
import com.xiaoyang.forum.domain.vo.ArticleDetailsVO;
import com.xiaoyang.forum.domain.vo.UserDetailsVO;
import com.xiaoyang.forum.domain.vo.UserVO;
import com.xiaoyang.forum.mapper.ArticleMapper;
import com.xiaoyang.forum.mapper.UserMapper;
import com.xiaoyang.forum.service.IUserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @Author: XiaoYang
 * @CreateTime: 2025-08-10
 * @Description: 用户服务实现类
 */
@Slf4j
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {

    /**
     * 手机验证码过期时间
     */
    @Value("${sms.code-expiration:5}")
    private Long emailCodeExpiration;

    /**
     * 手机验证码发送限制次数
     */
    @Value("${sms.send-limit:10}")
    private Integer sendLimit;

    /**
     * 是否发送手机验证码
     */
    @Value("${sms.is-send:false}")
    private Boolean isSend;

    @Value("${file.oss.downloadUrl}")
    private String downloadUrl;

    @Autowired
    private ArticleMapper articleMapper;

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private RedisUtil redisUtil;

    @Autowired
    private MailUtil mailUtil;

    /**
     * 注册用户
     * @param username
     * @param password
     */
    @Override
    public void registerUser(String username, String password) {
        // DTO转实体
        User user = new User();
        user.setUsername(username);
        user.setPassword(BCryptUtils.encryptPassword(password));

        // 1. 根据用户名判断用户是否存在
        Long count = getCount(user); // 查询用户是否存在
        if (count > 0) {
            throw new ApplicationException(Result.failed(ResultCode.AILED_USER_EXISTS));
        }

        // 2. 保存用户默认信息
        user.setNickname(user.getUsername());// 昵称
        user.setGender(2); // 性别
        user.setIsAdmin(0); // 普通用户
        user.setAvatarUrl(downloadUrl + "defaultavatar.jpg"); // 默认头像
        user.setArticleCount(0); // 发帖数
        user.setFans(0);
        user.setState(0); // 状态
        user.setCreateTime(LocalDateTime.now());
        user.setUpdateTime(LocalDateTime.now());

        // 3. 存储数据库
        userMapper.insert(user);
    }

    /**
     * 用户登录--账号密码
     * @param username
     * @param password
     * @return
     */
    @Override
    public String login(String username, String password) {
        // 1. 查询用户是否存在
        User user = getOne(username);
        if (user == null) { // 用户不存在
            throw new ApplicationException(Result.failed(ResultCode.FAILED_USER_NOT_EXISTS));
        }

        // 2. 验证密码
        if (!BCryptUtils.matchesPassword(password, user.getPassword())) { // 密码错误
            throw new ApplicationException(Result.failed(ResultCode.FAILED_LOGIN));
        }

        // 3. 登录成功
        log.info("用户登录成功" + user.getId());

        // 4. 生成JWT令牌
        String token = createToken(user);

        return token;
    }

    /**
     * 用户登录--验证码登录
     * @param email
     * @param code
     * @return
     */
    @Override
    public String codeLogin(String email, String code) {
        log.info("验证码登录", email);
        // 1. 校验验证码
        checkCode(email, code);

        // 2. 根据邮箱获取用户信息
        User user = getOne(new QueryWrapper<User>().eq("email", email));
        if (user == null) {
            log.info("用户不存在" + email);
            throw new ApplicationException(Result.failed(ResultCode.FAILED_USER_NOT_EXISTS));
        }

        // 3. 登录成功
        String token = createToken(user);

        return token;
    }

    /**
     * 修改用户头像
     * @param avatarUrl
     * @param userId
     * @return
     */
    @Override
    public int updateHeadImage(String avatarUrl, long userId) {
        log.info("修改用户头像：{}", avatarUrl);

        User user = userMapper.selectById(userId);
        if (user == null) {
            throw new ApplicationException(ResultCode.FAILED_USER_NOT_EXISTS.getMessage());
        }
        user.setAvatarUrl(avatarUrl);
        return userMapper.updateById(user);
    }

    /**
     * 获取用户信息
     * @param token
     * @return
     */
    @Override
    public UserVO getUserInfo(String token) {
        Integer userId = JWTUtil.getUserIdFromToken(token);
        log.info("获取用户信息" + userId);
        // 1. 根据用户id查询用户
        User user = getUser(userId);

        UserVO userVO = new UserVO();
        userVO.setId(user.getId());
        userVO.setUsername(user.getUsername());
        userVO.setNickname(user.getNickname());
        userVO.setGender(user.getGender());
        userVO.setIsAdmin(user.getIsAdmin());
        userVO.setAvatarUrl(downloadUrl + user.getAvatarUrl());
        userVO.setArticleCount(user.getArticleCount());
        userVO.setRemark(user.getRemark());
        userVO.setCreateTime(user.getCreateTime());

        return userVO;
    }

    /**
     * 获取用户信息--个人主页
     * @param userId
     * @return
     */
    @Override
    public UserDetailsVO getUserAndArticleInfo(Long userId) {
        // 1. 根据用户id查询用户
        User user = userMapper.selectById(userId);
        if (user == null) {
            log.info("用户不存在" + userId);
            throw new ApplicationException(Result.failed(ResultCode.FAILED_USER_NOT_EXISTS));
        }

        // 2. 根据用户id查询用户文章列表
        LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Article::getUserId, userId)
                .orderByDesc(Article::getCreateTime);
        List<Article> articleList = articleMapper.selectList(queryWrapper);

        UserDetailsVO userDetailsVO = new UserDetailsVO();
        userDetailsVO.setNickname(user.getNickname());
        userDetailsVO.setAvatarUrl(downloadUrl + user.getAvatarUrl());
        userDetailsVO.setRemark(user.getRemark());
        userDetailsVO.setArticleCount(user.getArticleCount());
        userDetailsVO.setFans(user.getFans());
        userDetailsVO.setCreateTime(user.getCreateTime());

        // 实体转VO
        CollUtil.toList(articleList, ArticleDetailsVO.class);
        List<ArticleDetailsVO> articleDetailsVOList = articleList.stream()
        .map(article -> {
            ArticleDetailsVO vo = new ArticleDetailsVO();
            vo.setId(article.getId());
            vo.setTitle(article.getTitle());
            vo.setContent(article.getContent());
            vo.setVisitCount(article.getVisitCount());
            vo.setReplyCount(article.getReplyCount());
            vo.setLikeCount(article.getLikeCount());
            vo.setCreateTime(article.getCreateTime());
            return vo;
        })
        .collect(Collectors.toList());

        userDetailsVO.setArticleDetailsVOList(articleDetailsVOList);

        return userDetailsVO;
    }

    /**
     * 修改用户信息
     * @param userId
     * @param userUpdateDTO
     * @return
     */
    @Override
    public int modifyUserInfo(long userId, UserUpdateDTO userUpdateDTO) {
        // 1. 校验用户是否存在
        User user = userMapper.selectById(userId);
        if (user == null) {
            log.info("用户不存在" + userId);
            throw new ApplicationException(Result.failed(ResultCode.FAILED_USER_NOT_EXISTS));
        }

        // 2. 校验邮箱格式
        if (!StrUtil.isEmpty(userUpdateDTO.getEmail())) {
            if (!RegexUtil.checkMail(userUpdateDTO.getEmail())) {
                log.info("邮箱格式错误" + userUpdateDTO.getEmail());
                throw new ApplicationException(Result.failed(ResultCode.FAILED_EMAIL));
            }
        }

        // 3. 校验手机号
        if (!StrUtil.isEmpty(userUpdateDTO.getPhone())) {
            if (!RegexUtil.isValidPhoneNumber(userUpdateDTO.getPhone())) {
                log.info("手机号格式错误" + userUpdateDTO.getPhone());
                throw new ApplicationException(Result.failed(ResultCode.FAILED_PHONE));
            }
        }

        user.setNickname(userUpdateDTO.getNickname()); // 昵称
        user.setGender(userUpdateDTO.getGender()); // 性别
        user.setRemark(userUpdateDTO.getRemark()); // 自我介绍
        user.setEmail(userUpdateDTO.getEmail()); // 邮箱
        user.setPhone(userUpdateDTO.getPhone()); // 手机号
        user.setUpdateTime(LocalDateTime.now()); // 更新时间
        return userMapper.updateById(user);
    }

    /**
     * 修改密码
     * @param userId
     * @param userPasswordDTO
     * @return
     */
    @Override
    public int modifyPassword(long userId, UserPasswordDTO userPasswordDTO) {
        // 1. 校验用户是否存在
        User user = userMapper.selectById(userId);
        if (user == null) {
            log.info("用户不存在" + userId);
            throw new ApplicationException(Result.failed(ResultCode.FAILED_USER_NOT_EXISTS));
        }

        // 2. 校验旧密码
        if (!BCryptUtils.matchesPassword(userPasswordDTO.getOldPassword(), user.getPassword())) {
            log.info("旧密码错误" + userPasswordDTO.getOldPassword());
            throw new ApplicationException(Result.failed(ResultCode.FAILED_PASSWORD_ERROR));
        }

        // 3. 校验新密码与旧密码是否一致
        if (userPasswordDTO.getOldPassword().equals(userPasswordDTO.getNewPassword())) {
            log.info("新密码与旧密码一致" + userPasswordDTO.getNewPassword());
            throw new RuntimeException(ResultCode.NEW_PASSWORD_SAME_AS_OLD.getMessage());
        }

        // 4. 校验两次输入的密码是否一致
        if (!userPasswordDTO.getNewPassword().equals(userPasswordDTO.getPasswordRepeat())) {
            log.info("两次输入的密码不一致" + userPasswordDTO.getNewPassword());
            throw new RuntimeException(ResultCode.FAILED_TWO_PWD_NOT_SAME.getMessage());
        }

        // 4. 修改密码
        user.setPassword(BCryptUtils.encryptPassword(userPasswordDTO.getNewPassword()));
        return userMapper.updateById(user);
    }

    /**
     * 发送验证码
     * @param email
     * @return
     */
    @Override
    public Boolean sendCode(String email) {
        log.info("发送验证码");

        // 1. 校验邮箱格式
        if (!RegexUtil.checkMail(email)) {
            log.info("邮箱格式错误" + email);
            throw new ApplicationException(Result.failed(ResultCode.FAILED_EMAIL));
        }

        // 2. 根据邮箱校验用户是否存在
        User user = getOne(new QueryWrapper<User>().eq("email", email));
        if (user == null) {
            log.info("邮箱不存在" + email);
            throw new ApplicationException(Result.failed(ResultCode.FAILED_USER_NOT_EXISTS));
        }

        String emailCodeKey = getEmail(email);
        Long expire = redisUtil.getExpire(emailCodeKey, TimeUnit.SECONDS); // 获取剩余时间
        if (expire != null && (emailCodeExpiration * 60 - expire) < 60) {
            throw new ApplicationException(ResultCode.FAILED_FREQUENT.getMessage());
        }

        // 限制验证码发送次数
        String codeTimeKey = getCodeTimeKey(email);
        Long sendTime = redisUtil.getCacheObject(codeTimeKey, Long.class);
        if (sendTime != null && sendTime >= sendLimit) {
            throw new ApplicationException(ResultCode.FAILED_TIME_LIMIT.getMessage());
        }

        // 2. 生成验证码
        String code = isSend ? RandomUtil.randomNumbers(6) : CacheConstants.DEFAULT_CODE;
        log.info("验证码：" + code);

        // 3. 保存验证码--存储redis
        redisUtil.setCacheObject(emailCodeKey, code, emailCodeExpiration, TimeUnit.MINUTES);

        // 4. 发送验证码
        String subject = "【XiaoYang】验证码:" + code
                + ",您正在使用邮箱验证码登录功能，验证码提供他人可能导致账号被盗，请勿转发或泄露。";

        // 4. 发送邮件
        if (isSend) {
            mailUtil.sendSampleMail(email, "验证码", subject);
        }

        // 存储发送次数
        redisUtil.increment(codeTimeKey);

        if (sendTime == null) { // 当天首次发送验证码
            long seconds = ChronoUnit.SECONDS.between(LocalDateTime.now(),
                    LocalDateTime.now().plusDays(1).withHour(0).withMinute(0).withSecond(0).withNano(0));
            redisUtil.expire(codeTimeKey, seconds, TimeUnit.SECONDS);
        }
        return true;
    }

    /**
     * 根据用户id查询用户
     * @param userId
     * @return
     */
    private User getUser(Integer userId) {
        User user = userMapper.selectById(userId);
        if (user == null) {
            log.info("用户不存在" + userId);
            throw new ApplicationException(Result.failed(ResultCode.FAILED_USER_NOT_EXISTS));
        }
        return user;
    }

    /**
     * 生成token
     * @param user
     * @return
     */
    private static String createToken(User user) {
        Map<String, Object> claims = new HashMap<>();
        claims.put("userId", user.getId());
        claims.put("username", user.getUsername());
        String token = JWTUtil.genJwt(claims);
        Integer userIdFromToken = JWTUtil.getUserIdFromToken(token);
        log.info("用户ID: " + userIdFromToken);
        return token;
    }

    /**
     * 校验验证码是否正确
     * @param email
     * @param code
     */
    private void checkCode(String email, String code) {
        // 获取手机验证码的key
        String emailCodeKey = getEmail(email);
        // 获取redis中存储的验证码
        String cacheCode = redisUtil.getCacheObject(emailCodeKey, String.class);
        // 验证码不存在或者已经是失效
        if (cacheCode == null) {
            throw new ApplicationException(ResultCode.FAILED_INVALID_CODE.getMessage());
        }
        // 输入的验证码和redis中存储的验证码不匹配
        if (!cacheCode.equals(code)) {
            throw new ApplicationException(ResultCode.FAILED_ERROR_CODE.getMessage());
        }
        // 验证码匹配，登录成功
        redisUtil.deleteObject(emailCodeKey); // 删除redis中存储的验证码
    }

    /**
     * 获取邮箱发送次数的key
     * @param email
     * @return
     */
    private String getCodeTimeKey(String email) {
        return CacheConstants.CODE_TIME_KEY + email;
    }

    /**
     * 获取邮箱验证码的key
     * @param email
     * @return
     */
    private String getEmail(String email) {
        return CacheConstants.EMAIL_CODE_KEY + email;
    }

    /**
     * 根据用户名查询用户
     * @param username
     * @return
     */
    private User getOne(String username) {
        return this.getOne(new LambdaQueryWrapper<User>()
                .eq(User::getUsername, username));
    }

    /**
     * 根据用户名查询用户是否存在
     * @param user
     * @return
     */
    private Long getCount(User user) {
        return userMapper.selectCount(new LambdaQueryWrapper<User>()
                .eq(User::getUsername, user.getUsername()));
    }
}
